package com.jin.webvideo.base.rpc;

import com.alibaba.fastjson.JSONObject;
import com.jin.webvideo.base.StringKeyValuePairs;
import com.jin.webvideo.base.httpclient.JsonHttpEntity;
import com.jin.webvideo.base.httpclient.ParamHttpEntity;
import com.jin.webvideo.base.httpclient.RequestBase;
import com.jin.webvideo.base.util.ReflectUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.FileEntity;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.*;

public class RpcMethodInterceptor implements MethodInterceptor {
    protected static final Logger logger = LoggerFactory.getLogger(RpcMethodInterceptor.class);
    private final HttpClient httpClient;
    private final String prefix;
    private final String[] header;

    protected RpcMethodInterceptor(HttpClient httpClient, String prefix, String[] header) {
        this.httpClient = httpClient;
        this.prefix = prefix;
        this.header = header;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws IOException {
        RequestMapping mapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
        Assert.isTrue(mapping.method().length > 0, "invalid method name" + mapping.method());

        Parameter[] parameters = method.getParameters();

        // 获取方法入参
        Map<String, Object> map = getParameterMap(parameters, objects);
        logger.debug("execute intercept method={} args={}", method, map);
        String path = "";
        if (mapping.path().length > 0) {
            path = mapping.path()[0];
        }

        // 处理restfull风格的url 获取URL, 存在PathVariable注解的映射到路径
        String uri = formatRestfullUrl(prefix, path, parameters, map);
        String httpMethod = mapping.method()[0].name();

        // 从入参中提取@Header 作为httpHeader
        Map<String, String> headers = headerParameters(parameters, map);
        headers.putAll(toHeaderMapper(mapping.headers()));
        headers.putAll(toHeaderMapper(this.header));
        String contentType = headers.get("content-type");

        // 获取@RequestBody作为数据，不存在则返回map
        Object data = getParameters(parameters, map);
        // 创建httpEntity
        HttpEntity httpEntity = createEntity(data, mapping.method()[0], contentType);

        HttpResponse response = request(URI.create(uri), httpMethod, headers, httpEntity);
        try {
            String str = getResponse(response);
            Class<?> returnType = method.getReturnType();
            if (Collection.class.isAssignableFrom(returnType)) {
                Type genericType = method.getGenericReturnType();
                if (genericType instanceof ParameterizedType parameterizedType) {
                    Type ata = parameterizedType.getActualTypeArguments()[0];
                    if (ata instanceof Class aClass) {
                        List list = JSONObject.parseArray(str, aClass);
                        return list;
                    }
                }
                throw new IOException("unsupported types");
            }
            return JSONObject.parseObject(str, method.getReturnType());
        }
        catch (IOException exception) {
            logger.warn("failed to execute intercept method={} exception={}", method, exception);
            throw exception;
        }
        finally {
            if (response instanceof Closeable closeable) {
                closeable.close();
            }
        }
    }

    private static String getResponse(HttpResponse response) throws IOException {
        if (response.getStatusLine().getStatusCode() == 200) {
            String str = EntityUtils.toString(response.getEntity());
            return str;
        }
        logger.debug("Invalid response data {}", EntityUtils.toString(response.getEntity()));
        throw new IOException("Invalid response");
    }

    private HttpEntity createEntity(Object map, RequestMethod method, String contentType) throws IOException {
        if (method == RequestMethod.GET || method == RequestMethod.DELETE) {
            return new ParamHttpEntity(map);
        }
        ContentType type = ContentType.getByMimeType(contentType);
        if (type == null) {
            type = ContentType.getByMimeType("*/*");
        }
        if (ContentType.APPLICATION_OCTET_STREAM.equals(type)) {
            return new FileEntity(filterFile(map));
        }
        if (ContentType.APPLICATION_JSON.equals(type)) {
            return new JsonHttpEntity(map);
        }
        if (ContentType.APPLICATION_FORM_URLENCODED.equals(type)) {
            return new UrlEncodedFormEntity(toNameValuePairs(map));
        }
        return new UrlEncodedFormEntity(toNameValuePairs(map));
    }

    private File filterFile(Object object) {

        if (object instanceof Map map) {
            Collection<Object> obj = map.values();
            Iterator<Object> iterator = obj.iterator();
            while (iterator.hasNext()) {
                if ( iterator.next() instanceof File file ) {
                    if (file.exists()) {
                        return file;
                    }
                }
            }
        }
        if ( object instanceof File file) {
            if (file.exists()) {
                return file;
            }
        }
        return null;
    }
    private List<? extends NameValuePair> toNameValuePairs(Object obj) throws IOException {
        try {
            Map<String, Object> map = ReflectUtil.toMap(obj);
            Set<String> keySet = map.keySet();
            List<NameValuePair> list = new ArrayList<>(keySet.size());
            for (String key : keySet) {
                Object val = map.get(key);
                list.add(new BasicNameValuePair(key, String.valueOf(val)));
            }
            return list;
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
            logger.warn("object={} write failed", obj);
        }
        throw new IOException("object write failed");
    }
    /**
     * headers 转化
     *
     * @param headers
     * @return
     */
    private Map<String, String> toHeaderMapper(String[] headers) {
        Map<String, String> map = new HashMap<>();
        for (String head : headers) {
            StringKeyValuePairs kvp = StringKeyValuePairs.createKeyValuePairs(head, "=");
            map.put(kvp.getKey().trim(), kvp.getValue().trim());
        }
        return map;
    }

    private Object getParameters(Parameter[] parameters, Map<String, Object> map) {
        for (Parameter parameter : parameters) {
            // 映射发生改变，更新映射
            RequestBody param = AnnotationUtils.getAnnotation(parameter, RequestBody.class);
            if (param != null) {
                Object object = map.get(parameter.getName());
                map.remove(parameter.getName());
                return object;
            }
        }
        return map;
    }

    private Map<String, String> headerParameters(Parameter[] parameters, Map<String, Object> map) {
        Map<String, String> header = new HashMap<>();
        for (Parameter parameter : parameters) {
            // 映射发生改变，更新映射
            Header param = AnnotationUtils.getAnnotation(parameter, Header.class);
            if (param != null) {
                String value = String.valueOf(map.get(parameter.getName()));
                header.put(param.key().isEmpty() ? parameter.getName() : param.key(), value);
                map.remove(parameter.getName());
            }
        }
        return header;
    }
    /**
     * 格式化restful风格url
     *
     * @param prefix
     * @param path
     * @param parameters
     * @param map
     * @return
     */
    private String formatRestfullUrl(String prefix, String path, Parameter[] parameters, Map<String, Object> map) {
        Map<String, Object> mapping = new HashMap<>();
        for (Parameter parameter : parameters) {
            // 映射发生改变，更新映射
            PathVariable param = AnnotationUtils.getAnnotation(parameter, PathVariable.class);
            if (param != null) {
                mapping.put(param.name().isEmpty() ? parameter.getName() : param.name(), map.get(parameter.getName()));
                map.remove(parameter.getName());
            }
        }

        StringTokenizer tokenizer = new StringTokenizer(path, "/");
        if (!prefix.equals("/")) {
            prefix = prefix + "/";
        }
        StringJoiner joiner = new StringJoiner("/", prefix, (path.endsWith("/") ? "/" : ""));
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            // 匹配restful {xxx}
            if (token.matches("^\\{[A-Za-z_][A-Za-z0-9_]*\\}$")) {
                token = String.valueOf(mapping.get(token.substring(1, token.length() - 1)));
            }
            joiner.add(token);
        }
        return joiner.toString();
    }

    /**
     * 获取方法入参列表
     *
     * @param parameters
     * @param params
     * @return
     */
    private Map<String, Object> getParameterMap(Parameter[] parameters, Object[] params) {
        Map<String, Object> map = new HashMap<>();
        int i = 0;
        for (Parameter parameter : parameters) {
            map.put(parameter.getName(), params[i++]);
        }
        return map;
    }

    private HttpResponse request(URI uri, String method, Map<String, String> header, HttpEntity entity) throws IOException {
        RequestBase request = new RequestBase(uri, method);
        if (header != null) {
            for (String key : header.keySet()) {
                request.setHeader(key, header.get(key));
            }
        }
        request.setEntity(entity);
        HttpResponse response = this.httpClient.execute(request);
        return response;
    }
}
